package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.FreemarkerService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.BehaviorConstants;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.article.vos.LoadArticleBehaviorVO;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    ApArticleMapper apArticleMapper;
    @Autowired
    ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    ApArticleContentMapper apArticleContentMapper;
    @Autowired
    FreemarkerService freemarkerService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    //计算分值   （阅读1分  点赞3分  评论5分  收藏8分）
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;
        if(apArticle.getLikes()!=null){
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if(apArticle.getViews()!=null){
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if(apArticle.getComment()!=null){
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if(apArticle.getCollection()!=null){
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }


    @Override
    public void updateArticleScore(ArticleVisitStreamMess mess) {
        //1.更新db中文章的行为数据
        ApArticle article = updateArticle(mess);

        //2.计算文章分值
        Integer score = computeScore(article);
        score = score*3;//提升当天对文章操作的  分值


        //3.处理当前文章对应的频道下热点文章
        replaceDataToRedis(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());

        //4.处理首页下的热点文章
        replaceDataToRedis(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 替换redis中文章分值
     * @param article
     * @param score
     * @param redisKey  缓存中的key
     */
    private void replaceDataToRedis(ApArticle article, Integer score, String redisKey) {
        //得到redis中  热点文章列表
        String hotArticleListJsonStr = stringRedisTemplate.boundValueOps(redisKey).get();

        //判断热点文章列表中是否存在  当前文章  如果存在则更新分值
        if(StringUtils.isNotBlank(hotArticleListJsonStr)){
            List<HotArticleVo> articleVoList = JSON.parseArray(hotArticleListJsonStr, HotArticleVo.class);

            boolean flag= false;//代表热点文章列表中是否存在  当前文章
            for (HotArticleVo hotArticleVo : articleVoList) {
                if(hotArticleVo.getId().equals(article.getId())){
                    hotArticleVo.setScore(score);//如果redis  热点文章列表中包含该文章 则更新分值
                    flag=true;
                    break;
                }
            }
            //如果不存在则添加到列表中
            if(!flag){
                HotArticleVo vo = new HotArticleVo();
                BeanUtils.copyProperties(article, vo);
                vo.setScore(score);
                articleVoList.add(vo);
            }

            //对列表重新排序 取前30条存入redis
            List<HotArticleVo> voList = articleVoList.stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .limit(30)
                    .collect(Collectors.toList());
            if(voList!=null && voList.size()>0){
                stringRedisTemplate.boundValueOps(redisKey).set(JSON.toJSONString(voList));
            }

        }
    }

    /**
     * 更新db中文章 的行为数据
     * @param mess
     * @return
     */
    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        Long articleId = mess.getArticleId();
        ApArticle article = getById(articleId);
        article.setLikes(article.getLikes()==null ? mess.getLike() : article.getLikes() + mess.getLike());
        article.setViews(article.getViews()==null ? mess.getView() : article.getViews() + mess.getView());
        article.setComment(article.getComment()==null ? mess.getComment() : article.getComment() + mess.getComment());
        article.setCollection(article.getCollection()==null ? mess.getCollect() : article.getCollection() + mess.getCollect());
        updateById(article);
        return article;
    }

    @Override
    public ResponseResult loadArticleBehavior(ArticleInfoDto dto) {
        //1.校验
        if(dto==null || dto.getArticleId()==null || dto.getAuthorId()==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //查询文章详情 的行为数据时，  需要判断必须登陆吗
        // 前提条件是
        // 1.网关解析令牌得到用户id放入请求头然后路由到behavior微服务
        // 2.behaviro服务中要有拦截器  在前置拦截方法中获取请求头中的用户id  放入threadlocal，  拦截器需要生效
        WmUser user = WmThreadLocalUtil.getUser();
        log.info("------ {}",user);
        if(user==null){
            //说明没登录
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        if(user.getId()==0){
            //说明点的不登录先看看
            LoadArticleBehaviorVO vo = new LoadArticleBehaviorVO();
            return ResponseResult.okResult(vo);
        }


        //2.查询redis 行为数据
        LoadArticleBehaviorVO vo = new LoadArticleBehaviorVO();

        String authorId = dto.getAuthorId().toString();//被关注人  作者id
        String userId = user.getId().toString();//当前登录人 id
        String articleId = dto.getArticleId().toString();//文章id

        BoundSetOperations<String, String> setOps = stringRedisTemplate.boundSetOps(BehaviorConstants.LIKE_BEHAVIOR + articleId);
        Boolean member = setOps.isMember(userId);
        if(member!=null && member){
            //说明当前登录人对 该文章做过点赞
            vo.setIslike(true);
        }

        BoundSetOperations<String, String> setFollowOps = stringRedisTemplate.boundSetOps(BehaviorConstants.APUSER_FOLLOW_RELATION + userId);
        Boolean member1 = setFollowOps.isMember(authorId);
        if(member1!=null && member1){
            //说明当前登录人关注了 作者
            vo.setIsfollow(true);
        }

        //3.返回
        return ResponseResult.okResult(vo);
    }

    @Override
    public ResponseResult load2(ArticleHomeDto dto, Short loadtypeLoadMore, boolean firstPage) {
        if(firstPage){
            String jsonString = stringRedisTemplate.boundValueOps(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag()).get();
            if(StringUtils.isNotBlank(jsonString)){
                List<HotArticleVo> hotArticleVos = JSON.parseArray(jsonString, HotArticleVo.class);
                return ResponseResult.okResult(hotArticleVos);
            }
        }
        return load(dto, loadtypeLoadMore);
    }

    @Override
    public ResponseResult load(ArticleHomeDto dto, Short type) {
        //1.校验
        if(dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        if(dto.getSize()==null || dto.getSize()<=0){
            dto.setSize(10);//默认10条
        }
        if(dto.getMinBehotTime()==null){
            dto.setMinBehotTime(new Date());
        }
        if(dto.getMaxBehotTime()==null){
            dto.setMaxBehotTime(new Date());
        }
        if(StringUtils.isBlank(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);//代表查询所有频道
        }


        //2.查询文章数据
        List<ApArticle> list =  apArticleMapper.loadArticleList(dto, type);


        //3.封装结果返回
        return ResponseResult.okResult(list);
    }


    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        //1.校验
        if(dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //其他校验....


        ApArticle article = new ApArticle();
        BeanUtils.copyProperties(dto, article);
        //2.判断是否存在id
        if(dto.getId()==null){
            //2.1不存在则新增
            save(article);//保存文章

            ApArticleConfig apArticleConfig = new ApArticleConfig(article.getId());
            apArticleConfigMapper.insert(apArticleConfig);//保存文章配置

            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(article.getId());
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.insert(apArticleContent);//保存文章内容
        }else{
            //2.2存在id则修改
            updateById(article);//修改文章

            //安装文章id查询 文章的内容
            ApArticleContent articleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, article.getId()));
            articleContent.setContent(dto.getContent());
            apArticleContentMapper.updateById(articleContent);//修改文章的内容
        }

        //开启新线程执行 详情页静态化处理
        freemarkerService.buildArticleContentPageAndToMinio(article, dto.getContent());



        //3.返回
        return ResponseResult.okResult(article.getId().toString());//需要将article_id 转字符串 返回   保存到自媒体wm_new表的article_id字段上
    }
}
